1 /*
2  * Collie - An asynchronous event-driven network framework using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd 
5  *
6  * Developer: putao's Dlang team
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 module collie.codec.http.httpmessage;
12 
13 import collie.codec.http.headers;
14 import collie.codec.http.exception;
15 
16 import std.typecons;
17 import std.typetuple;
18 import std.socket;
19 import std.variant;
20 import std.conv;
21 import std.exception;
22 import std.string;
23 
24 alias HTTPMessage = HttpMessage;
25 
26 enum HttpMessageType
27 {
28 	unknown,
29 	request,
30 	response
31 }
32 
33 /**
34 */
35 class HttpMessage
36 {
37 	private HttpMessageType _messageType;
38 	this()
39 	{
40 		initilizeVersion();
41 		_messageType = HttpMessageType.unknown;
42 	}
43 
44 	this(HttpMessageType messageType)
45 	{
46 		initilizeVersion();
47 		_messageType = messageType;
48 		if(messageType == HttpMessageType.response)
49 			_resreq.res = Response();
50 		else if(messageType == HttpMessageType.request)
51 			_resreq.req = Request();
52 	}
53 
54 	private void initilizeVersion()
55 	{
56 		_version[0] = 1;
57 		_version[1] = 1;
58 		_versionStr = "1.1";
59 	}
60 
61 	/* Setter and getter for the SPDY priority value (0 - 7).  When serialized
62    * to SPDY/2, Codecs will collpase 0,1 -> 0, 2,3 -> 1, etc.
63    *
64    * Negative values of pri are interpreted much like negative array
65    * indexes in python, so -1 will be the largest numerical priority
66    * value for this SPDY version (i.e. 3 for SPDY/2 or 7 for SPDY/3),
67    * -2 the second largest (i.e. 2 for SPDY/2 or 6 for SPDY/3).
68    */
69 	enum byte kMaxPriority = 7;
70 
71 	//	static byte normalizePriority(byte pri) {
72 	//		if (pri > kMaxPriority || pri < -kMaxPriority) {
73 	//			// outside [-7, 7] => highest priority
74 	//			return kMaxPriority;
75 	//		} else if (pri < 0) {
76 	//			return pri + kMaxPriority + 1;
77 	//		}
78 	//		return pri;
79 	//	}
80 
81 	/**
82    * Is this a chunked message? (fpreq, fpresp)
83    */
84 	@property void chunked(bool chunked)
85 	{
86 		_chunked = chunked;
87 	}
88 
89 	@property bool chunked() const
90 	{
91 		return _chunked;
92 	}
93 
94 	/**
95    * Is this an upgraded message? (fpreq, fpresp)
96    */
97 	@property void upgraded(bool upgraded)
98 	{
99 		_upgraded = upgraded;
100 	}
101 
102 	@property bool upgraded() const
103 	{
104 		return _upgraded;
105 	}
106 
107 	/**
108    * Set/Get client address
109    */
110 	@property void clientAddress(Address addr)
111 	{
112 		request()._clientAddress = addr;
113 		request()._clientIP = addr.toAddrString();
114 		request()._clientPort = addr.toPortString;
115 	}
116 
117 	@property Address clientAddress()
118 	{
119 		return request()._clientAddress;
120 	}
121 
122 	string getClientIP()
123 	{
124 		return request()._clientIP;
125 	}
126 
127 	string getClientPort()
128 	{
129 		return request()._clientPort;
130 	}
131 
132 	/**
133    * Set/Get destination (vip) address
134    */
135 	@property void dstAddress(Address addr)
136 	{
137 		_dstAddress = addr;
138 		_dstIP = addr.toAddrString;
139 		_dstPort = addr.toPortString;
140 	}
141 
142 	@property Address dstAddress()
143 	{
144 		return _dstAddress;
145 	}
146 
147 	string getDstIP()
148 	{
149 		return _dstIP;
150 	}
151 
152 	string getDstPort()
153 	{
154 		return _dstPort;
155 	}
156 
157 	/**
158    * Set/Get the local IP address
159    */
160 	@property void localIp(string ip)
161 	{
162 		_localIP = ip;
163 	}
164 
165 	@property string localIp()
166 	{
167 		return _localIP;
168 	}
169 
170 	@property void method(HTTPMethod method)
171 	{
172 		request()._method = method;
173 	}
174 
175 	@property HTTPMethod method()
176 	{
177 		return request()._method;
178 	}
179 	//void setMethod(folly::StringPiece method);
180 
181 	string methodString()
182 	{
183 		return method_strings[request()._method];
184 	}
185 
186 	void setHTTPVersion(ubyte maj, ubyte min)
187 	{
188 		_version[0] = maj;
189 		_version[1] = min;
190 		_versionStr = format("%d.%d", maj, min);
191 	}
192 
193 	auto getHTTPVersion()
194 	{
195 		Tuple!(ubyte, "maj", ubyte, "min") tv;
196 		tv.maj = _version[0];
197 		tv.min = _version[1];
198 		return tv;
199 	}
200 
201 	string getProtocolVersion()
202 	{
203 		return _versionStr;
204 	}
205 
206 	@property void url(string url)
207 	{
208 		auto idx = url.indexOf('?');
209 		if (idx > 0)
210 		{
211 			request()._path = url[0 .. idx];
212 			request()._query = url[idx + 1 .. $];
213 		}
214 		else
215 		{
216 			request()._path = url;
217 		}
218 		request()._url = url;
219 	}
220 
221 	@property string url()
222 	{
223 		return request()._url;
224 	}
225 
226 	@property wantsKeepAlive()
227 	{
228 		return _wantsKeepalive;
229 	}
230 
231 	@property wantsKeepAlive(bool klive)
232 	{
233 		_wantsKeepalive = klive;
234 	}
235 	/**
236    * Access the path component (fpreq)
237    */
238 	string getPath()
239 	{
240 		return request()._path;
241 	}
242 
243 	/**
244    * Access the query component (fpreq)
245    */
246 	string getQueryString()
247 	{
248 		return request()._query;
249 	}
250 
251 	@property void statusMessage(string msg)
252 	{
253 		response()._statusMsg = msg;
254 	}
255 
256 	@property string statusMessage()
257 	{
258 		return response()._statusMsg;
259 	}
260 
261 	/**
262    * Access the status code (fpres)
263    */
264 	@property void statusCode(ushort status)
265 	{
266 		response()._status = status;
267 	}
268 
269 	@property ushort statusCode()
270 	{
271 		return response()._status;
272 	}
273 
274 	@property string host()
275 	{
276 		return _headers.getSingleOrEmpty(HTTPHeaderCode.HOST);
277 	}
278 
279 	/**
280    * Access the headers (fpreq, fpres)
281    */
282 	ref HttpHeaders getHeaders()
283 	{
284 		return _headers;
285 	}
286 
287 	void addHeader(HTTPHeaderCode code, string value)
288 	{
289 		_headers.add(code, value);
290 	}
291 
292 	void addHeader(string name, string value)
293 	{
294 		_headers.add(name, value);
295 	}
296 
297 	void setHeader(HTTPHeaderCode code, string value)
298 	{
299 		_headers.set(code, value);
300 	}
301 
302 	void setHeader(string name, string value)
303 	{
304 		_headers.set(name, value);
305 	}
306 
307 	/**
308    * Decrements Max-Forwards header, when present on OPTIONS or logDebug methods.
309    *
310    * Returns HTTP status code.
311    */
312 	int processMaxForwards()
313 	{
314 		auto m = method();
315 		if (m == HTTPMethod.HTTP_logDebug || m == HTTPMethod.HTTP_OPTIONS)
316 		{
317 			string value = _headers.getSingleOrEmpty(HTTPHeaderCode.MAX_FORWARDS);
318 			if (value.length > 0)
319 			{
320 				long max_forwards = -1;
321 
322 				collectException(to!long(value), max_forwards);
323 
324 				if (max_forwards < 0)
325 				{
326 					return 400;
327 				}
328 				else if (max_forwards == 0)
329 				{
330 					return 501;
331 				}
332 				else
333 				{
334 					_headers.set(HTTPHeaderCode.MAX_FORWARDS, to!string(max_forwards - 1));
335 				}
336 			}
337 		}
338 		return 0;
339 	}
340 
341 	/**
342    * Returns true if the version of this message is HTTP/1.0
343    */
344 	bool isHTTP1_0() const
345 	{
346 		return _version[0] == 1 && _version[1] == 0;
347 	}
348 
349 	/**
350    * Returns true if the version of this message is HTTP/1.1
351    */
352 	bool isHTTP1_1() const
353 	{
354 		return _version[0] == 1 && _version[1] == 1;
355 	}
356 
357 	/**
358    * Returns true if this is a 1xx response.
359    */
360 	bool is1xxResponse()
361 	{
362 		return (statusCode() / 100) == 1;
363 	}
364 
365 	/**
366    * Fill in the fields for a response message header that the server will
367    * send directly to the client.
368    *
369    * @param version           HTTP version (major, minor)
370    * @param statusCode        HTTP status code to respond with
371    * @param msg               textual message to embed in "message" status field
372    * @param contentLength     the length of the data to be written out through
373    *                          this message
374    */
375 	void constructDirectResponse(ubyte maj, ubyte min, const int statucode,
376 			string statusMsg, int contentLength = 0)
377 	{
378 		statusCode(cast(ushort) statucode);
379 		statusMessage(statusMsg);
380 		constructDirectResponse(maj, min, contentLength);
381 	}
382 
383 	/**
384    * Fill in the fields for a response message header that the server will
385    * send directly to the client. This function assumes the status code and
386    * status message have already been set on this HTTPMessage object
387    *
388    * @param version           HTTP version (major, minor)
389    * @param contentLength     the length of the data to be written out through
390    *                          this message
391    */
392 	void constructDirectResponse(ubyte maj, ubyte min, int contentLength = 0)
393 	{
394 		setHTTPVersion(maj, min);
395 		_headers.set(HTTPHeaderCode.CONTENT_LENGTH, to!string(contentLength));
396 		if (!_headers.exists(HTTPHeaderCode.CONTENT_TYPE))
397 		{
398 			_headers.add(HTTPHeaderCode.CONTENT_TYPE, "text/plain");
399 		}
400 		chunked(false);
401 		upgraded(false);
402 	}
403 
404 	/**
405    * Check if query parameter with the specified name exists.
406    */
407 	bool hasQueryParam(string name)
408 	{
409 		parseQueryParams();
410 		return _queryParams.get(name, string.init) != string.init;
411 	}
412 	/**
413    * Get the query parameter with the specified name.
414    *
415    * Returns a reference to the query parameter value, or
416    * proxygen::empty_string if there is no parameter with the
417    * specified name.  The returned value is only valid as long as this
418    * HTTPMessage object.
419    */
420 	string getQueryParam(string name, string defaults = string.init)
421 	{
422 		parseQueryParams();
423 		return _queryParams.get(name, defaults);
424 	}
425 	/**
426    * Get the query parameter with the specified name after percent decoding.
427    *
428    * Returns empty string if parameter is missing or folly::uriUnescape
429    * query param
430    */
431 	string getDecodedQueryParam(string name)
432 	{
433 		import std.uri;
434 
435 		parseQueryParams();
436 		string v = _queryParams.get(name, string.init);
437 		if (v == string.init)
438 			return v;
439 		return decodeComponent(v);
440 	}
441 
442 	/**
443    * Get the query parameter with the specified name after percent decoding.
444    *
445    * Returns empty string if parameter is missing or folly::uriUnescape
446    * query param
447    */
448 	string[string] queryParam()
449 	{
450 		parseQueryParams();
451 		return _queryParams;
452 	}
453 
454 	void queryParam(string[string] v)
455 	{
456 		_queryParams = v;
457 	}
458 
459 	/**
460    * Set the query string to the specified value, and recreate the url_.
461    *
462    */
463 	void setQueryString(string query)
464 	{
465 		unparseQueryParams();
466 		request._query = query;
467 	}
468 	/**
469    * Remove the query parameter with the specified name.
470    *
471    */
472 	void removeQueryParam(string name)
473 	{
474 		parseQueryParams();
475 		_queryParams.remove(name);
476 	}
477 
478 	/**
479    * Sets the query parameter with the specified name to the specified value.
480    *
481    * Returns true if the query parameter was successfully set.
482    */
483 	void setQueryParam(string name, string value)
484 	{
485 		parseQueryParams();
486 		_queryParams[name] = value;
487 	}
488 
489 	/**
490    * @returns true if this HTTPMessage represents an HTTP request
491    */
492 	bool isRequest() const
493 	{
494 		return _messageType == HttpMessageType.request;
495 	}
496 
497 	/**
498    * @returns true if this HTTPMessage represents an HTTP response
499    */
500 	bool isResponse() const
501 	{
502 		return _messageType == HttpMessageType.response;
503 	}
504 
505 	static string statusText(int code)
506 	{
507 		switch (code)
508 		{
509 		case 100:
510 			return "Continue";
511 		case 101:
512 			return "Switching Protocols";
513 		case 102:
514 			return "Processing"; // RFC2518
515 		case 103:
516 			return "Early Hints"; 
517 		case 200:
518 			return "OK";
519 		case 201:
520 			return "Created";
521 		case 202:
522 			return "Accepted";
523 		case 203:
524 			return "Non-Authoritative logInformation";
525 		case 204:
526 			return "No Content";
527 		case 205:
528 			return "Reset Content";
529 		case 206:
530 			return "Partial Content";
531 		case 207:
532 			return "Multi-Status"; // RFC4918
533 		case 208:
534 			return "Already Reported"; // RFC5842
535 		case 226:
536 			return "IM Used"; // RFC3229
537 		case 300:
538 			return "Multiple Choices";
539 		case 301:
540 			return "Moved Permanently";
541 		case 302:
542 			return "Found";
543 		case 303:
544 			return "See Other";
545 		case 304:
546 			return "Not Modified";
547 		case 305:
548 			return "Use Proxy";
549 		case 306:
550 			return "Reserved";
551 		case 307:
552 			return "Temporary Redirect";
553 		case 308:
554 			return "Permanent Redirect"; // RFC7238
555 		case 400:
556 			return "Bad Request";
557 		case 401:
558 			return "Unauthorized";
559 		case 402:
560 			return "Payment Required";
561 		case 403:
562 			return "Forbidden";
563 		case 404:
564 			return "Not Found";
565 		case 405:
566 			return "Method Not Allowed";
567 		case 406:
568 			return "Not Acceptable";
569 		case 407:
570 			return "Proxy Authentication Required";
571 		case 408:
572 			return "Request Timeout";
573 		case 409:
574 			return "Conflict";
575 		case 410:
576 			return "Gone";
577 		case 411:
578 			return "Length Required";
579 		case 412:
580 			return "Precondition Failed";
581 		case 413:
582 			return "Request Entity Too Large";
583 		case 414:
584 			return "Request-URI Too Long";
585 		case 415:
586 			return "Unsupported Media Type";
587 		case 416:
588 			return "Requested Range Not Satisfiable";
589 		case 417:
590 			return "Expectation Failed";
591 		case 418:
592 			return "I'm a teapot"; // RFC2324
593 		case 422:
594 			return "Unprocessable Entity"; // RFC4918
595 		case 423:
596 			return "Locked"; // RFC4918
597 		case 424:
598 			return "Failed Dependency"; // RFC4918
599 		case 425:
600 			return "Reserved for WebDAV advanced collections expired proposal"; // RFC2817
601 		case 426:
602 			return "Upgrade Required"; // RFC2817
603 		case 428:
604 			return "Precondition Required"; // RFC6585
605 		case 429:
606 			return "Too Many Requests"; // RFC6585
607 		case 431:
608 			return "Request Header Fields Too Large"; 	// RFC6585
609 		case 451:
610 			return "Unavailable For Legal Reasons"; 	// RFC7725
611 		case 500:
612 			return "Internal Server Error";
613 		case 501:
614 			return "Not Implemented";
615 		case 502:
616 			return "Bad Gateway";
617 		case 503:
618 			return "Service Unavailable";
619 		case 504:
620 			return "Gateway Timeout";
621 		case 505:
622 			return "HTTP Version Not Supported";
623 		case 506:
624 			return "Variant Also Negotiates"; // RFC2295
625 		case 507:
626 			return "Insufficient Storage"; // RFC4918
627 		case 508:
628 			return "Loop Detected"; // RFC5842
629 		case 510:
630 			return "Not Extended"; // RFC2774
631 		case 511:
632 			return "Network Authentication Required"; // RFC6585
633 		default:
634 			if (code >= 600)
635 				return "Unknown";
636 			if (code >= 500)
637 				return "Unknown server error";
638 			if (code >= 400)
639 				return "Unknown error";
640 			if (code >= 300)
641 				return "Unknown redirection";
642 			if (code >= 200)
643 				return "Unknown success";
644 			if (code >= 100)
645 				return "Unknown information";
646 			return "  ";
647 		}
648 	}
649 
650 protected:
651 	/** The 12 standard fields for HTTP messages. Use accessors.
652    * An HTTPMessage is either a Request or Response.
653    * Once an accessor for either is used, that fixes the type of HTTPMessage.
654    * If an access is then used for the other type, a DCHECK will fail.
655    */
656 	struct Request
657 	{
658 		Address _clientAddress;
659 		string _clientIP;
660 		string _clientPort;
661 		HTTPMethod _method = HTTPMethod.HTTP_INVAILD;
662 		string _path;
663 		string _query;
664 		string _url;
665 	}
666 
667 	struct Response
668 	{
669 		ushort _status = 200;
670 		string _statusStr;
671 		string _statusMsg;
672 	}
673 
674 	ref Request request()
675 	{
676 		// FIXME: Needing refactor or cleanup -@zxp at 5/24/2018, 1:09:52 PM
677 		// 
678 		if (_messageType == HttpMessageType.unknown)
679 		{
680 			_messageType = HttpMessageType.request;
681 			_resreq.req = Request();
682 		}
683 		else if (_messageType == HttpMessageType.response)
684 		{
685 			throw new HTTPMessageTypeException("the message type is Response not Request");
686 		}
687 		return _resreq.req;
688 	}
689 
690 	ref Response response()
691 	{
692 		if (_messageType == HttpMessageType.unknown)
693 		{
694 			_messageType = HttpMessageType.response;
695 			_resreq.res = Response();
696 		}
697 		else if (_messageType == HttpMessageType.request)
698 		// if (_messageType != HttpMessageType.response)
699 		{
700 			throw new HTTPMessageTypeException("the message type is Request not Response");
701 		}
702 
703 		return _resreq.res;
704 	}
705 
706 protected:
707 	//void parseCookies(){}
708 
709 	void parseQueryParams()
710 	{
711 		import collie.utils.string;
712 
713 		if (_parsedQueryParams)
714 			return;
715 		_parsedQueryParams = true;
716 		string query = getQueryString();
717 		if (query.length == 0)
718 			return;
719 		splitNameValue(query, '&', '=', (string name, string value) {
720 			name = strip(name);
721 			value = strip(value);
722 			_queryParams[name] = value;
723 			return true;
724 		});
725 	}
726 
727 	void unparseQueryParams()
728 	{
729 		_queryParams.clear();
730 		_parsedQueryParams = false;
731 	}
732 
733 	union Req_Res
734 	{
735 		Request req;
736 		Response res;
737 	}
738 
739 private:
740 	Address _dstAddress;
741 	string _dstIP;
742 	string _dstPort;
743 
744 	string _localIP;
745 	string _versionStr;
746 	Req_Res _resreq;
747 	ubyte[2] _version;
748 	HttpHeaders _headers;
749 	string[string] _queryParams;
750 
751 	bool _parsedCookies = false;
752 	bool _parsedQueryParams = false;
753 	bool _chunked = false;
754 	bool _upgraded = false;
755 	bool _wantsKeepalive = true;
756 }
757 
758 /**
759 */
760 enum HttpStatusCodes
761 {
762 	CONTINUE = 100,
763 	SWITCHING_PROTOCOLS = 101,
764 	PROCESSING = 102,            // RFC2518
765 	EARLY_HINTS = 103,           // RFC8297
766 
767 	OK = 200,
768 	CREATED = 201,
769 	ACCEPTED = 202,
770 	NON_AUTHORITATIVE_INFORMATION = 203,
771 	NO_CONTENT = 204,
772 	RESET_CONTENT = 205,
773 	PARTIAL_CONTENT = 206,
774 	MULI_STATUS = 207,
775 	ALREADY_REPORTED = 208,      // RFC5842
776 	IM_USED = 226,               // RFC3229
777 
778 	MULTIPLE_CHOICES = 300,
779 	MOVED_PERMANENTLY = 301,
780 	FOUND = 302,
781 	SEE_OTHER = 303,
782 	NOT_MODIFIED = 304,
783 	USE_PROXY = 305,
784 	TEMPORARY_REDIRECT = 307,
785 	PERMANENTLY_REDIRECT = 308,					// RFC7238
786 
787 	BAD_REQUEST = 400,
788 	UNAUTHORIZED = 401,
789 	PAYMENT_REQUIRED = 402,
790 	FORHIDDEN = 403,
791 	NOT_FOUND = 404,
792 	METHOD_NOT_ALLOWED = 405,
793 	NOT_ACCEPTABLE = 406,
794 	PROXY_AUTHENTICATION_REQUIRED = 407,
795 	REQUEST_TIMEOUT = 408,
796 	CONFLICT = 409,
797 	GONE = 410,
798 	LENGTH_REQUIRED = 411,
799 	PRECONDITION_FAILED = 412,
800 	REQUEST_ENTITY_TOO_LARGE = 413,
801 	REQUEST_URI_TOO_LARGE = 414,
802 	UNSUPPORTED_MEDIA_TYPE = 415,
803 	REQUESTED_RANGE_NOT_SATISFIABLE = 416,
804 	EXPECTATION_FAILED = 417,
805 	I_AM_A_TEAPOT = 418,
806 	MISDIRECTED_REQUEST = 421,
807 	UNPROCESSABLE_ENTITY = 422,
808 	LOCKED = 423,
809 	FAILED_DEPENDENCY = 424,
810 	RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425,
811 	UPGRADE_REQUIRED = 426,
812 	PRECONDITION_REQUIRED = 428,				// RFC6585
813 	TOO_MANY_REQUESTS = 429,					// RFC6585
814 	REQUEST_HEADER_FIELDS_TOO_LARGE = 431,		// RFC6585
815 	UNAVAILABLE_FOR_LAGAL_REASONS = 451,
816 
817 	INTERNAL_SERVER_ERROR = 500,
818 	NOT_IMPLEMENTED = 501,
819 	BAD_GATEWAY = 502,
820 	SERVICE_UNAVALIBALE = 503,
821 	GATEWAY_TIMEOUT = 504,
822 	VERSION_NOT_SUPPORTED = 505,
823 	VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506, // RFC2295
824 	INSUFFICIENT_STORAGE = 507,					// RFC4918
825 	LOOP_DETECTED = 508,						// RFC5842
826 	NOT_EXTENDED = 510,							// RFC2774
827 	NETWORK_AUTHENTICATION_REQUIRED = 511		// RFC6585
828 }
829 
830 bool isSuccessCode(HttpStatusCodes code)
831 {
832 	return (code >= 200 && code < 300);
833 }